
# Import
from __future__ import with_statement
from threading import *
from Utils import *

# BMaxPrecompiler
class BMaxPrecompiler:
    
    def __init__(self, app, config):
        self.importFiles = dict()
        self.importModules = dict()
        #self.globals = list()
        self.types = dict()
        self.ifDefs = dict()
        self.types[""] = list()         # Global namespace
        self.app = app
        self.varHeader = ""             # For BB
        
        # General
        self.precompEnabled = int(config.get("General", "Precompiler"))
        self.threadSafe = int(config.get("General", "Threading"))
        self.deletePreFiles = int(config.get("General", "DeletePreFiles"))
        
        # Components
        self.generateProfilerData = int(config.get("Components", "Profiler"))
        self.generateCodeStats = int(config.get("Components", "CodeStats"))
        self.debugFunctions = int(config.get("Components", "DebugFunctions"))
        
        # Profiler
        self.debug = int(config.get("Profiler", "DebugProfiler"))
        self.topX = config.get("Profiler", "TopX")
        self.minimumCalls = config.get("Profiler", "MinimumCalls")
        self.supportCollIDE = int(config.get("Profiler", "CollIDE"))
        self.realTimeData = int(config.get("Profiler", "RealTimeData"))
        self.floatPrecision = config.get("Profiler", "AverageTimeFloatPrecision")
        
        # CollIDE
        if self.supportCollIDE:
            self.deletePreFiles = False
            config.set("General", "StartCompiler", "0")
        
        # HTML
        self.style = config.get("HTML", "Style")
        self.timeCritical = int(config.get("HTML", "TimeCritical"))
        
        self.lineCount = 0
        self.functionCount = 0
        self.methodCount = 0
        self.toDoCount = 0
        
        # Locks
        self.lock_lineCount = Lock()
        self.lock_methodCount = Lock()
        self.lock_functionCount = Lock()
        self.lock_toDoCount = Lock()
        self.lock_importFiles = Lock()
        self.lock_importModules = Lock()
        self.lock_types = Lock()
        self.lock_ifDefs = Lock()
        
        if os.name == "nt":
            self.ifDefs["win32"] = True
        elif os.name == "posix":
            self.ifDefs["linux"] = True
        elif os.name == "mac":
            self.ifDefs["macos"] = True
        
        # TODO: MacOS
        
        if self.app.isDebugMode:
            self.ifDefs["debug"] = True
        
        if self.debug:
            print "\nIfDefs: " + str(self.ifDefs)
        
    def setMainFile(self, mainFile):
        self.mainFile = mainFile
        self.mainFileDir = fixPath(extractDir(mainFile) + "/")
        self.extension = mainFile[mainFile.rfind("."):]
        self.profilerLogName = "ProfilerLog_" + getCleanName(stripAll(mainFile))
        
        self.isBB = False
        self.isBMAX = False
        
        if self.extension == ".bb":
            self.isBB = True
        elif self.extension == ".bmx":
            self.isBMAX = True
        
        if self.isBMAX:
            self.lineSeparator = ";"
            self.singleComment = "'"
        elif self.isBB:
            self.lineSeparator = ":"
            self.singleComment = ";"
    
    def writeData(self, stream, prefix = "", suffix = "\n"):
        stream.write(prefix + "Files: " + str(len(self.importFiles) + 1) + suffix)              # + 1 because the main file isn't included
        stream.write(prefix + "Lines: " + str(self.lineCount) + suffix)
        if self.toDoCount:
            stream.write(prefix + "ToDo: " + str(self.toDoCount) + suffix)
        stream.write(prefix + "Functions: " + str(self.functionCount) + suffix)
        stream.write(prefix + "Types: " + str(len(self.types) - 1) + suffix)                    # - 1 because global namespace is also stored in this list
        if self.isBMAX:
            stream.write(prefix + "Methods: " + str(self.methodCount) + suffix)
            stream.write(prefix + "Modules: " + str(len(self.importModules)) + suffix)
        
    def writeDataHTML(self, stream, prefix = "", suffix = "\n"):
        stream.write(prefix + "<table>" + suffix)
        stream.write(prefix + "<tr><td>Files</td><td>" + str(len(self.importFiles) + 1) + "</td></tr>" + suffix)              # + 1 because the main file isn't included
        stream.write(prefix + "<tr><td>Lines</td><td>" + str(self.lineCount) + "</td></tr>" + suffix)
        if self.toDoCount:
            stream.write(prefix + "<tr><td>ToDo</td><td>" + str(self.toDoCount) + "</td></tr>" + suffix)
        stream.write(prefix + "<tr><td>Functions</td><td>" + str(self.functionCount) + "</td></tr>" + suffix)
        stream.write(prefix + "<tr><td>Types</td><td>" + str(len(self.types) - 1) + "</td></tr>" + suffix)                    # - 1 because global namespace is also stored in this list
        if self.isBMAX:
            stream.write(prefix + "<tr><td>Methods</td><td>" + str(self.methodCount) + "</td></tr>" + suffix)
            stream.write(prefix + "<tr><td>Modules</td><td>" + str(len(self.importModules)) + "</td></tr>" + suffix)
        stream.write(prefix + "</table>" + suffix)
    
    def getFunctionProfilerEnd(self, profilerFuncName):
        if self.generateProfilerData:
            if self.isBMAX:
                return "profiler_time_all_" + profilerFuncName + " :+ (MilliSecs() - profiler_time_" + profilerFuncName + ")"
            elif self.isBB:
                return "profiler_time_all_" + profilerFuncName + " = " + "profiler_time_all_" + profilerFuncName + " + (MilliSecs() - profiler_time_" + profilerFuncName + ")"
        else:
            return ""
        
    def openLocal(self, fileName, mode):
        try:
            return open(self.app.dir + fileName, mode)
        except IOError:
            return open("." + fileName, mode)
        
    def createProfilerFile(self, filename):
        print "\nCreating profilerLog.pre" + self.extension + "\n"
        
        out = open(filename, "w")
        
        def writeHTML(data):
            out.write("WriteLine profiler_stream_html, \"" + data + "\"\n")
        
        #out.write("SuperStrict\n")
        
        #for item in self.importFiles.keys():
        #    out.write("Import \"" + item.replace(".bmx", ".pre.bmx") + "\"\n")
        
        # Added by blitzcoder (30.06.2008)
        ################################################################
        # Open socketstream to localhost to profile in realtime
        if self.isBMAX and self.realTimeData:
            out.write("Global profiler_socket:TSocket = CreateTCPSocket()\n")
            out.write("Const profiler_socketPort:Int = 11113\n")
            out.write("Const profiler_localhost:Int = 2130706433\n")
            out.write("ConnectSocket profiler_socket, profiler_localhost, profiler_socketPort\n")
            out.write("Global profiler_socketStream:TSocketStream = CreateSocketStream(profiler_socket)\n")
            out.write("\n")
        ################################################################
        
        if self.isBMAX:
            streamType = ":TStream"
            stringCast = "String"
            stringSlice = "[..length+point]"
        elif self.isBB:
            streamType = ""
            stringCast = "Str"
            stringSlice = ""
        
        cssStyle = ""
        cssFile = self.openLocal("/styles/" + self.style + ".css", "r")
        #cssStyle = cssFile.readlines()
        for line in cssFile:
            cssStyle += line#.rstrip() + "\n"
        cssFile.close()
        
        if self.isBB:
            cssStyle = cssStyle.replace("\"", "\" + Chr(34) + \"")
            cssStyle = cssStyle.replace(";", "\" + Chr(59) + \"")
            cssStyle = cssStyle.replace("\n", "\" + Chr(10) + \"")
            #cssStyle = cssStyle.replace("\t", "\" + Chr(32) + \"")
        elif self.isBMAX:
            cssStyle = cssStyle.replace("\"", "\" + Chr(34) + \"")
            cssStyle = cssStyle.replace("\n", "~n")
            
        if self.debug:
            print "CSS: " + cssStyle
        
        # BB needs all global vars in one file
        if self.isBB:
            out.write(self.varHeader + "\n")
        
        out.write("""
Function Profiler_AverageFloat$(stri$, length%)
    Local point% = Instr(stri, ".")
    
    If point > (Len(stri) - length)
        Return Replace(LSet(stri, length + point), " ", "0")
    EndIf
    
    Return stri""" + stringSlice + """
End Function
""")

        # BMax sort algorithm
        if self.isBMAX:
            # Arrays
            out.write("Global profiler_mostCalls:String[" + self.topX + ", 2]\n")
            out.write("Global profiler_mostTotalTime:String[" + self.topX + ", 2]\n")
            out.write("Global profiler_mostAverageTime:String[" + self.topX + ", 2]\n")
            out.write("Global profiler_mostMemory:String[" + self.topX + ", 2]\n")
            out.write("Global profiler_valueArgs:String\n")
            # Algorithm
            out.write("""

' Initialize with -1 values; Arrays have the same size
For Local prof_Index:Int = 0 To profiler_mostCalls.length / 2 - 1
    profiler_mostCalls[prof_Index, 0] = "-1"
    profiler_mostTotalTime[prof_Index, 0] = "-1"
    profiler_mostAverageTime[prof_Index, 0] = "-1"
    profiler_mostMemory[prof_Index, 0] = "-1"
Next

Function Profiler_InsertValue(val:String, name:String, arr:String[,]) 
	Local countHighest:Int = arr.length / 2
	Local I:Int
	
	For I = 0 To countHighest
		If I < countHighest And Float(val) > Float(arr[I, 0])
			Continue
		ElseIf I > 0
			For Local H:Int = 0 To I - 2
				arr[H, 0] = arr[H + 1, 0]
				arr[H, 1] = arr[H + 1, 1]
			Next
			arr[I - 1, 0] = val
			arr[I - 1, 1] = name
			Exit
		Else
			Exit
		EndIf
	Next
End Function
""")
        
        out.write("\n\nFunction " + self.profilerLogName + "()\nprofiler_programTotalRunTime = MilliSecs() - profiler_programTotalRunTime\n\nLocal profiler_stream" + streamType + " = WriteFile(\"" + stripAll(self.mainFile) + ".profiler.txt\")\nWriteLine profiler_stream, \"" + self.app.title + " " + self.app.version + " (www.blitzprog.com)\"\nWriteLine profiler_stream, \"\"\n")

        if self.isBMAX:
            out.write("Local profiler_memEnd:Int = GCMemAlloced()\n")
            out.write("Local I:Int = 0\n")
        elif self.isBB:
            out.write("Local I% = 0\n")
        
        # All calls
        out.write("Local profiler_allCalls% = 0\n")
        
        # Func type
        if self.isBMAX:
            out.write("Local cssStyle:String = \"\"\n")
            out.write("Local funcsNotCalled:String = \"\"\n")
            if self.realTimeData:
                out.write('WriteLine profiler_socketStream,"[PROF][END]\n"');
                out.write("CloseSocket(profiler_socket)\n");
                out.write("profiler_socketStream.close()\n");
        elif self.isBB:
            out.write("Local cssStyle$ = \"\"\n")
            out.write("Local funcsNotCalled$ = \"\"\n")
            
        out.write("Local profiler_average$ = \"\"\n")
        out.write("Local profiler_stream_html" + streamType + " = WriteFile(\"" + stripAll(self.mainFile) + ".profiler.html\")\nWriteLine profiler_stream_html, \"<html><head><style>" + cssStyle + "</style></head><body>" + self.app.title + " " + self.app.version + " (<a href='http://www.blitzprog.com'>www.blitzprog.com</a>)<br />\"\n")
        
        # Navigation
        if self.generateCodeStats:
            codeStatsLink = "<a href='#CodeStats'>Code stats</a> | "
        else:
            codeStatsLink = ""
        writeHTML("<p>" + codeStatsLink + "<a href='#Types'>Types</a> | <a href='#Graphs'>Graphs</a> | <a href='#Functions'>Functions</a> | <a href='#Debugging'>Debugging</a></p>")
        
        # Code stats
        if self.generateCodeStats:
            out.write("WriteLine profiler_stream_html, \"<h2 id='CodeStats'>Code stats</h2>\"\n")
            self.writeDataHTML(out, "WriteLine profiler_stream_html, \"", "\"\n")
        
        # Types
        writeHTML("<h2 id='Types'>Types</h2><ul class='type_list'>")
        for type in self.types.keys():
            if type:
                writeHTML("<li><a href='#" + type + "'>" + type + "</a></li>")
        writeHTML("</ul>")

        # Sort array
        if self.isBMAX:
            for type, methodList in self.types.items():
                if type:
                    out.write("Profiler_InsertValue(String(SizeOf(" + type + ")), \"" + type + "\", profiler_mostMemory)\n")
                if len(methodList) == 0:
                    continue
                for function, funcLineCount in methodList:
                    prefix = type + "."
                    varName = prefix.replace(".", "__") + function
                    out.write("profiler_allCalls :+ " + prefix + "profiler_calls_" + varName + "\n")
                    out.write("profiler_average$ = Profiler_AverageFloat(" + stringCast + "(Ceil(" + prefix + "profiler_time_all_" + varName + " / ((" + prefix + "profiler_calls_" + varName + "=0) + Float(" + prefix + "profiler_calls_" + varName + ")) * 100) / 100), " + self.floatPrecision + ")\n")
                    out.write("If " + prefix + "profiler_calls_" + varName + " >= " + self.minimumCalls + " Then Profiler_InsertValue(profiler_average$, \"" + prefix + function + "\", profiler_mostAverageTime)\n")
                    out.write("Profiler_InsertValue(String(" + prefix + "profiler_calls_" + varName + "), \"" + prefix + function + "\", profiler_mostCalls)\n")
                    out.write("Profiler_InsertValue(String(" + prefix + "profiler_time_all_" + varName + "), \"" + prefix + function + "\", profiler_mostTotalTime)\n")

        # Prevent division by zero
        out.write("If profiler_allCalls = 0 Then profiler_allCalls = 1\n")
        out.write("If profiler_programTotalRunTime = 0 Then profiler_programTotalRunTime = 1\n")
        
        # Table header
        separatorTH = "<th class='separator'></th>"
        separatorTD = "<td class='separator'></td>"
        
        # Graphs
        writeHTML("<h2 id='Graphs'>Graphs</h2>")
        writeHTML("<p><a href='#HighestAverageTime'>Highest average time</a> | <a href='#MostCalls'>Most calls</a> | <a href='#HighestTotalTime'>Highest total time</a> | <a href='#HighestMemoryUsage'>Highest memory usage</a></p>")
        if self.generateProfilerData:
            if self.isBMAX:
                # Most average time
                writeHTML("<h3 id='HighestAverageTime'>Highest average time</h3>")
                out.write("profiler_valueArgs = \"\"\n")
                out.write("For I = profiler_mostAverageTime.length / 2 - 1 To 0 Step -1\n")
                out.write("    If profiler_mostAverageTime[I, 0] = \"-1\" Then Continue\n")
                out.write("    profiler_valueArgs :+ (profiler_mostAverageTime.length / 2 - I) + \",\" + profiler_mostAverageTime[I, 0] + \",\"\n")
                out.write("Next\n")
                out.write("profiler_valueArgs = profiler_valueArgs[..profiler_valueArgs.length-1]\n")
                out.write("WriteLine profiler_stream_html, \"<img src='http://blitzprog.com/scripts/barchart.php?width=460&values=\" + profiler_valueArgs + \"' alt='Highest average time (requires internet connection)'></img>\"\n")
                writeHTML("<table class='type_table'>")
                writeHTML("<tbody><tr><th>#</th><th>Function</th><th>Average time</th></tr></tbody>")
                out.write("For I = profiler_mostAverageTime.length / 2 - 1 To 0 Step -1\n")
                out.write("    WriteLine profiler_stream_html, \"<tr><td>\" + (profiler_mostAverageTime.length / 2 - I) + \"</td><td class='func_name'><a href='#\" + profiler_mostAverageTime[I, 1] + \"'>\" + profiler_mostAverageTime[I, 1] + \"</a></td><td class='average_time graph_column_width'>\" + profiler_mostAverageTime[I, 0] + \"</td></tr>\"\n")
                out.write("Next\n")
                writeHTML("</table>")
                writeHTML("<p class='note'>The table does not include functions that were called less than " + self.minimumCalls + " times.</p>")
                
                # Most calls
                writeHTML("<h3 id='MostCalls'>Most calls</h3>")
                out.write("profiler_valueArgs = \"\"\n")
                out.write("For I = profiler_mostCalls.length / 2 - 1 To 0 Step -1\n")
                out.write("    If profiler_mostCalls[I, 0] = \"-1\" Then Continue\n")
                out.write("    profiler_valueArgs :+ (profiler_mostCalls.length / 2 - I) + \",\" + profiler_mostCalls[I, 0] + \",\"\n")
                out.write("Next\n")
                out.write("profiler_valueArgs = profiler_valueArgs[..profiler_valueArgs.length-1]\n")
                out.write("WriteLine profiler_stream_html, \"<img src='http://blitzprog.com/scripts/barchart.php?width=460&values=\" + profiler_valueArgs + \"' alt='Most calls (requires internet connection)'></img>\"\n")
                writeHTML("<table class='type_table'>")
                writeHTML("<tbody><tr><th>#</th><th>Function</th><th>Calls</th>" + separatorTH + "<th>% of all calls</th></tr></tbody>")
                out.write("For I = profiler_mostCalls.length / 2 - 1 To 0 Step -1\n")
                out.write("    WriteLine profiler_stream_html, \"<tr><td>\" + (profiler_mostCalls.length / 2 - I) + \"</td><td class='func_name'><a href='#\" + profiler_mostCalls[I, 1] + \"'>\" + profiler_mostCalls[I, 1] + \"</a></td><td class='calls graph_column_width'>\" + profiler_mostCalls[I, 0] + \"</td>" + separatorTD + "<td class='percentage'>\" + Profiler_AverageFloat(" + stringCast + "(100 * Float(profiler_mostCalls[I, 0]) / profiler_allCalls), " + self.floatPrecision + ") + \"</td></tr>\"\n")
                out.write("Next\n")
                writeHTML("</table>")
                out.write("WriteLine profiler_stream_html, \"<p class='note'>Total number of function calls: \" + profiler_allCalls + \"</p>\"\n")
                out.write("WriteLine profiler_stream_html, \"<p class='note'>Function calls per millisecond (average): \" + (profiler_allCalls / profiler_programTotalRunTime) + \"</p>\"\n")

                # Most total time
                writeHTML("<h3 id='HighestTotalTime'>Highest total time</h3>")
                out.write("profiler_valueArgs = \"\"\n")
                out.write("For I = profiler_mostTotalTime.length / 2 - 1 To 0 Step -1\n")
                out.write("    If profiler_mostTotalTime[I, 0] = \"-1\" Then Continue\n")
                out.write("    profiler_valueArgs :+ (profiler_mostTotalTime.length / 2 - I) + \",\" + profiler_mostTotalTime[I, 0] + \",\"\n")
                out.write("Next\n")
                out.write("profiler_valueArgs = profiler_valueArgs[..profiler_valueArgs.length-1]\n")
                out.write("WriteLine profiler_stream_html, \"<img src='http://blitzprog.com/scripts/barchart.php?width=460&values=\" + profiler_valueArgs + \"' alt='Highest total time (requires internet connection)'></img>\"\n")
                writeHTML("<table class='type_table'>")
                writeHTML("<tbody><tr><th>#</th><th>Function</th><th>Total time</th>" + separatorTH + "<th>% of program run time</th></tr></tbody>")
                out.write("For I = profiler_mostTotalTime.length / 2 - 1 To 0 Step -1\n")
                out.write("    WriteLine profiler_stream_html, \"<tr><td>\" + (profiler_mostTotalTime.length / 2 - I) + \"</td><td class='func_name'><a href='#\" + profiler_mostTotalTime[I, 1] + \"'>\" + profiler_mostTotalTime[I, 1] + \"</a></td><td class='total_time graph_column_width'>\" + profiler_mostTotalTime[I, 0] + \"</td>" + separatorTD + "<td class='percentage'>\" + Profiler_AverageFloat(" + stringCast + "(100 * Float(profiler_mostTotalTime[I, 0]) / profiler_programTotalRunTime), " + self.floatPrecision + ") + \"</td></tr>\"\n")
                out.write("Next\n")
                writeHTML("</table>")

                # Highest memory usage
                writeHTML("<h3 id='HighestMemoryUsage'>Highest memory usage (Types)</h3>")
                out.write("profiler_valueArgs = \"\"\n")
                out.write("For I = profiler_mostMemory.length / 2 - 1 To 0 Step -1\n")
                out.write("    If profiler_mostMemory[I, 0] = \"-1\" Then Continue\n")
                out.write("    profiler_valueArgs :+ (profiler_mostMemory.length / 2 - I) + \",\" + profiler_mostMemory[I, 0] + \",\"\n")
                out.write("Next\n")
                out.write("profiler_valueArgs = profiler_valueArgs[..profiler_valueArgs.length-1]\n")
                out.write("WriteLine profiler_stream_html, \"<img src='http://blitzprog.com/scripts/barchart.php?width=460&values=\" + profiler_valueArgs + \"' alt='Highest memory usage (requires internet connection)'></img>\"\n")
                writeHTML("<table class='type_table'>")
                writeHTML("<tbody><tr><th>#</th><th>Type</th><th>Size</th></tr></tbody>")
                out.write("For I = profiler_mostMemory.length / 2 - 1 To 0 Step -1\n")
                out.write("    WriteLine profiler_stream_html, \"<tr><td>\" + (profiler_mostMemory.length / 2 - I) + \"</td><td class='func_name'><a href='#\" + profiler_mostMemory[I, 1] + \"'>\" + profiler_mostMemory[I, 1] + \"</a></td><td class='memory_usage graph_column_width'>\" + profiler_mostMemory[I, 0] + \"</td></tr>\"\n")
                out.write("Next\n")
                writeHTML("</table>")
            elif self.isBB:
                writeHTML("<p>This feature does only exist for Blitz Max.</p>")
        else:
            writeHTML("<p>Profiler data generation has been disabled.</p>")
        
        # Profiler data
        if self.generateProfilerData:
            profilerHeader = "<th>Average</th><th>Total</th><th>Calls</th>"
        else:
            profilerHeader = ""
        
        # Code stats
        if self.generateCodeStats:
            codeStatsHeader = "<th>Lines</th>"
        else:
            codeStatsHeader = ""
            
        out.write("WriteLine profiler_stream_html, \"<h2 id='Functions'>Functions</h2>\"\n")
        
        oldPrefix = ""
        breakLine = ""
        
        # Table header
        tableIntro = "<table class='type_table'><tbody><tr><th>Name</th>" + separatorTH + profilerHeader + separatorTH + codeStatsHeader + "<th class='separator end'></th></tr></tbody>"
        writeHTML(tableIntro)
        
        # Iterate through all types
        count = 0
        for type, methodList in self.types.items():
            
            if len(methodList) == 0:
                continue
            
            for function, funcLineCount in methodList:
                count += 1
                prefix = type + "." #function[:function.find(".") + 1]
                #print " [" + prefix + "] <> [" + oldPrefix + "]"
                
                varName = prefix.replace(".", "__") + function
                
                if self.isBB:
                    prefix = ""
                
                # TXT
                out.write("WriteLine profiler_stream, LSet(" + "\"" + function + ": \", 30) + RSet(" + prefix + "profiler_time_all_" + varName + " / ((" + prefix + "profiler_calls_" + varName + "=0) + " + prefix + "profiler_calls_" + varName + "), 10) + \" ms | Total: \" + RSet(" + prefix + "profiler_time_all_" + varName + ", 7) + \" ms | Calls: \" + " + "RSet(" + prefix + "profiler_calls_" + varName + ", 6) + \"\"\n")
                
                # Create empty line if type changed
                if count == 1:
                    breakLine = ""
                elif prefix == oldPrefix:
                    breakLine = ""
                else:
                    breakLine = "</table><h3 id='" + type + "'>" + type + "</h3><p>Memory usage: \" + SizeOf(" + type + ") + \" bytes</p>" + tableIntro
                        
                oldPrefix = prefix
                
                # Write breakLine
                if breakLine:
                    writeHTML(breakLine)
                
                # HTML
                #colorLine = "\"" + "<span style='color:" + "\"" + " + Hex(((" + prefix + "profiler_time_all_" + varName + " / ((" + prefix + "profiler_calls_" + varName + "=0) + " + prefix + "profiler_calls_" + varName + ") >= " + str(self.timeCritical) + ")*255) Shl 16 + (((" + prefix + "profiler_calls_" + varName + " > 0) And (" + prefix + "profiler_time_all_" + varName + " = 0)) * 192) Shl 8 + ((" + prefix + "profiler_calls_" + varName + " = 0) * $B0B0B0)) + " + "\"" + "'>" + "\""
                #out.write("WriteLine profiler_stream_html, " + colorLine + " + Replace(LSet(" + "\"" + function + ": \", 30) + RSet(" + prefix + "profiler_time_all_" + varName + " / ((" + prefix + "profiler_calls_" + varName + "=0) + " + prefix + "profiler_calls_" + varName + "), 10) + \" ms | Total: \" + RSet(" + prefix + "profiler_time_all_" + varName + ", 7) + \" ms | Calls: \" + " + "RSet(" + prefix + "profiler_calls_" + varName + ", 6), \" \", \"&nbsp;\")" + " + " + "\"" + "</span>\"\n")
                if self.generateProfilerData:
                    # CSS style
                    out.write("If " + prefix + "profiler_calls_" + varName + " = 0\n")
                    out.write("    cssStyle = \"not_called\"\n")
                    out.write("    funcsNotCalled = funcsNotCalled + \"<li><a href='#" + prefix + function + "'>" + prefix + function + "</a></li>\"\n")
                    out.write("ElseIf " + prefix + "profiler_time_all_" + varName + " = 0\n")
                    out.write("    cssStyle = \"fast\"\n")
                    out.write("ElseIf (" + prefix + "profiler_time_all_" + varName + " / " + prefix + "profiler_calls_" + varName + ") >= " + str(self.timeCritical) + "\n")
                    out.write("    cssStyle = \"critical\"\n")
                    out.write("Else\n")
                    out.write("    cssStyle = \"normal\"\n")
                    out.write("EndIf\n")
                else:
                    out.write("cssStyle = \"normal\"\n")
                
                if self.generateProfilerData:
                    out.write("profiler_average$ = Profiler_AverageFloat(" + stringCast + "(Ceil(" + prefix + "profiler_time_all_" + varName + " / ((" + prefix + "profiler_calls_" + varName + "=0) + Float(" + prefix + "profiler_calls_" + varName + ")) * 100) / 100), " + self.floatPrecision + ")\n")
                    profilerData = "<td class='average_time\' title='Average time / ms'>\" + profiler_average + \"</td><td class='total_time' title='Total time / ms'>\" + " + prefix + "profiler_time_all_" + varName + " + \"</td><td class='calls' title='Calls'>\" + " + prefix + "profiler_calls_" + varName + " + \"</td>"
                else:
                    profilerData = ""
                
                # TODO: Implement code stats
                #codeStatsData = ""
                if self.generateCodeStats:
                    codeStatsData = "<td class='func_lines' title='Lines'>" + str(funcLineCount) + "</td>" # TODO: Line count for every method
                else:
                    codeStatsData = ""
                
                writeHTML("<tr class='\" + cssStyle + \"'><td id='" + prefix + function + "' class='func_name' title='Function name'>" + function + "</td>" + separatorTD + profilerData + separatorTD + codeStatsData + "<td class='separator end'></td></tr>")
        
        out.write("WriteLine profiler_stream_html, \"</table>\"\n")
        
        # Debugging
        writeHTML("<h2 id='Debugging'>Debugging</h2>")
        if self.isBMAX:
            writeHTML("<h3 id='GeneralInformation'>General information</h3>")
            # TODO: New CSS style
            writeHTML("<table class='type_table'>")
            out.write("WriteLine profiler_stream_html, \"<tr><td>Run time:</td><td style='text-align: right;'>\" + profiler_programTotalRunTime + \" ms</td></tr>\"\n")
            out.write("WriteLine profiler_stream_html, \"<tr><td>GCMemAlloced at the beginning:</td><td style='text-align: right;'>\" + profiler_memBeginning + \" bytes</td></tr>\"\n")
            out.write("WriteLine profiler_stream_html, \"<tr><td>GCMemAlloced at the end:</td><td style='text-align: right;'>\" + profiler_memEnd + \" bytes</td></tr>\"\n")
            #out.write("WriteLine profiler_stream_html, \"<p>&nbsp;</p>\"\n")
            out.write("WriteLine profiler_stream_html, \"<tr><td>AppArgs:</td><td style='text-align: left;'><ul>\"\n")
            out.write("For Local profiler_arg:String = EachIn AppArgs\n")
            out.write("    WriteLine profiler_stream_html, \"<li>[\" + profiler_arg + \"]</li>\"\n")
            out.write("Next\n")
            out.write("WriteLine profiler_stream_html, \"</ul></td></tr>\"\n")
            out.write("WriteLine profiler_stream_html, \"<tr><td>AppDir:</td><td style='text-align: left;'>\" + AppDir + \"</td></tr>\"\n")
            out.write("WriteLine profiler_stream_html, \"<tr><td>AppFile:</td><td style='text-align: left;'>\" + AppFile + \"</td></tr>\"\n")
            out.write("WriteLine profiler_stream_html, \"<tr><td>AppTitle:</td><td style='text-align: left;'>\" + AppTitle + \"</td></tr>\"\n")
            out.write("WriteLine profiler_stream_html, \"<tr><td>LaunchDir:</td><td style='text-align: left;'>\" + LaunchDir + \"</td></tr>\"\n")
            out.write("WriteLine profiler_stream_html, \"</table>\"\n")
        writeHTML("<h3 id='FuncsNotCalled'>Functions with 0 calls</h3>")
        # TODO: New CSS style
        writeHTML("<ul class='type_list'>")
        out.write("WriteLine profiler_stream_html, funcsNotCalled\n")
        writeHTML("</ul>")
        
        out.write("CloseFile profiler_stream_html\n")
        out.write("CloseFile profiler_stream\n")
        out.write("End Function\n")
        
        out.close()
    
    def scan(self, file, tabLevel = ""):
        file = fixPath(file)
        infoTitle = "\n" + tabLevel + "Scanning: " + os.path.basename(file) + ""
        infoSubLine = ""
        #for i in range(2, len(infoTitle)):
        #    infoSubLine += "-"
        print tabLevel[:-2] + infoSubLine + tabLevel + infoTitle
        
        # Set working directory
        self.currentWorkingDir = extractDir(file) + "/"
        print tabLevel + "Current dir: " + self.currentWorkingDir
        if self.debug:
            print tabLevel + "Path: " + file
        
        inp = open(file, "r")
        line = ""
        addToLine = "" 
        
        inDisabledIfDefBlock = 0
        inRemBlock = 0
        #inExternBlock = 0
        
        for line in inp:
            
            # Remove comments
            if self.isBMAX:
                pos = findIgnStr(line, "'")
            elif self.isBB:
                pos = findIgnStr(line, ";")
                
            if pos != -1:
                line = line[:pos]
            
            # Trim
            line = line.strip()
            
            if addToLine:
                line = addToLine + line
                addToLine = ""
            
            # Skip empty lines
            if len(line) == 0:
                continue
            
            # IfDef
            if line[0] == "?":
                if line == "?":
                    inDisabledIfDefBlock = False
                    if self.debug:
                        print tabLevel + "EndIfDef " + ifDefParam
                else:
                    # Using NOT operator
                    # TODO: Merge to a smaller version depending on a condition variable
                    if line[1:4].lower() == "not" and not isVarChar(line[4]):
                        ifDefParam = line[5:]
                        ifDefParamLower = ifDefParam.lower()

                        if self.debug:
                            print tabLevel + "IfNDef " + ifDefParam

                        # TODO: Try/Except
                        if (not ifDefParamLower in self.ifDefs) or (not self.ifDefs[ifDefParamLower]):
                            inDisabledIfDefBlock = False
                            if self.debug:
                                print tabLevel + ifDefParam + " == 0 [Enabled]"
                        else:
                            inDisabledIfDefBlock = True
                            if self.debug:
                                print tabLevel + ifDefParam + " != 0 [Disabled]"
                    else:
                        ifDefParam = line[1:]
                        ifDefParamLower = ifDefParam.lower()

                        if self.debug:
                            print tabLevel + "IfDef " + ifDefParam
                    
                        # TODO: Try/Except
                        if (not ifDefParamLower in self.ifDefs) or (not self.ifDefs[ifDefParamLower]):
                            inDisabledIfDefBlock = True
                            if self.debug:
                                print tabLevel + ifDefParam + " == 0 [Disabled]"
                        else:
                            inDisabledIfDefBlock = False
                            if self.debug:
                                print tabLevel + ifDefParam + " != 0 [Enabled]"
                continue
            
            # In disabled ifdef block
            if inDisabledIfDefBlock:
                continue
            
            # Multiple lines
            if line.endswith(".."):
                self.addToLine = line[:-2]
                continue
            
            # Ignore rem blocks
            if inRemBlock:
                if startswith(line, "endrem") or startswith(line, "end rem"):
                    inRemBlock = False
                continue
            
            # Ignore extern blocks
            #if self.inExternBlock:
            #    if line == "EndExtern" or line == "End Extern":
            #        self.inExternBlock = False
            #    self.out.write(line + "\n")
            #    continue
            
            # Rem
            if startswith(line, "rem"):
                inRemBlock = True
                continue
            
            # Import
            if startswith(line, "import") or startswith(line, "include"):
                importFile = getParameter(line, 0)
                if importFile.startswith("\""):
                    importFile = importFile[1:len(importFile)-1]
                    
                    if importFile.endswith(self.extension):
                        print tabLevel + "Import.File: " + importFile
                        
                        if self.isBMAX:
                            if os.path.isabs(importFile):
                                importFile = fixPath(importFile)  # Is absolute
                            else:
                                importFile = fixPath(os.path.abspath(self.currentWorkingDir + importFile))  # Was relative, make absolute
                        elif self.isBB:
                            importFile = fixPath(os.path.abspath(self.mainFileDir + importFile))  # Was relative, make absolute
                        
                        if self.debug:
                            print tabLevel + "Import.Path: " + importFile
                        
                        # If it already exists, ignore it
                        if importFile in self.importFiles:
                            continue
                        
                        self.importFiles[importFile] = len(self.importFiles)       # File import
                        
                        savedWorkDir = self.currentWorkingDir
                        self.scan(importFile, tabLevel + "  ")
                        #if self.debug:
                        #    print tabLevel + "Resume: " + savedWorkDir
                        print tabLevel + infoSubLine
                        self.currentWorkingDir = savedWorkDir
                    else:
                        print tabLevel + "Import.Lib: " + importFile
                else:
                    print tabLevel + "Import.Mod: " + importFile
                    self.importModules[importFile] = len(self.importModules)       # Module import
        
        inp.close()
    
    # MUST BE THREAD-SAFE
    def precompile(self, file, isMain = False):
        infoTitle = "\nPrecompiling: " + os.path.basename(file) + "\n"
        if not self.threadSafe:
            for i in range(2, len(infoTitle)):
                infoTitle += "-"
        print infoTitle
        
        if isMain:
            self.profilerLogIncludeWritten = False
        else:
            self.profilerLogIncludeWritten = True
        
        encoding = getEncoding(file)
        if encoding:
            print "Encoding: " + encoding
        
        inp = codecs.open(file, "r", encoding=encoding)
        out = codecs.open(extractDir(file) + "/" + os.path.basename(file)[:-len(self.extension)] + ".pre" + self.extension, "w", encoding=encoding)
        line = ""
        enabled = self.precompEnabled
        lineCount = 0
        inMethod = 0
        inFunction = 0
        isMethod = 0
        isFunction = 0
        inDisabledIfDefBlock = 0
        inRemBlock = 0
        inExternBlock = 0
        addToLine = ""                         # Used for multi-lines
        ifDefParam = ""
        funcName = ""
        profilerFuncName = ""
        funcStartLine = 0
        currentType = ""
        
        with self.lock_types:
            currentTypeMethodList = self.types[""]
        
        try:
            for line in inp:
                lineCount += 1
                
                # Remove comments
                if self.isBMAX:
                    pos = findIgnStr(line, "'")
                elif self.isBB:
                    pos = findIgnStr(line, ";")
                    
                if pos != -1:
                    # Disable precompiler
                    comment = line[pos+1:].strip()
                    if comment == "PreCompOff":
                        enabled = 0
                    elif comment == "PreCompOn":
                        enabled = 1
                    elif comment.lower().startswith("todo"):
                        with self.lock_toDoCount:
                            self.toDoCount += 1
                    line = line[:pos]
                
                # Precompiler currently disabled
                if not enabled:
                    out.write(line) # No trim, thus no '+ "\n"'
                    continue
                
                # Trim
                line = line.strip()
                
                if addToLine:
                    line = addToLine + line
                    addToLine = ""
                
                # Skip empty lines
                if len(line) == 0:
                    out.write(line + "\n")
                    continue
                
                # IfDef
                if line[0] == "?":
                    if line == "?":
                        inDisabledIfDefBlock = False
                        if self.debug:
                            print tabLevel + "EndIfDef " + ifDefParam
                    else:
                        # Using NOT operator
                        # TODO: Merge to a smaller version depending on a condition variable
                        if line[1:4].lower() == "not" and not isVarChar(line[4]):
                            ifDefParam = line[5:]
                            ifDefParamLower = ifDefParam.lower()

                            if self.debug:
                                print tabLevel + "IfNDef " + ifDefParam

                            # TODO: Try/Except
                            with self.lock_ifDefs:
                                if (not ifDefParamLower in self.ifDefs) or (not self.ifDefs[ifDefParamLower]):
                                    inDisabledIfDefBlock = False
                                    if self.debug:
                                        print tabLevel + ifDefParam + " == 0 [Enabled]"
                                else:
                                    inDisabledIfDefBlock = True
                                    if self.debug:
                                        print tabLevel + ifDefParam + " != 0 [Disabled]"
                        else:
                            ifDefParam = line[1:]
                            ifDefParamLower = ifDefParam.lower()

                            if self.debug:
                                print tabLevel + "IfDef " + ifDefParam
                        
                            # TODO: Try/Except
                            with self.lock_ifDefs:
                                if (not ifDefParamLower in self.ifDefs) or (not self.ifDefs[ifDefParamLower]):
                                    inDisabledIfDefBlock = True
                                    if self.debug:
                                        print tabLevel + ifDefParam + " == 0 [Disabled]"
                                else:
                                    inDisabledIfDefBlock = False
                                    if self.debug:
                                        print tabLevel + ifDefParam + " != 0 [Enabled]"
                    out.write(self.singleComment + line + "\n")
                    continue
                
                # In disabled ifdef block
                if inDisabledIfDefBlock:
                    out.write(self.singleComment + line + "\n")
                    continue
                
                # Multiple lines
                if line.endswith(".."):
                    if self.debug:
                        print " Multiple lines"
                    addToLine = line[:-2]
                    continue
                
                # Ignore rem blocks
                if inRemBlock:
                    if startswith(line, "endrem") or startswith(line, "end rem"):
                        if self.debug:
                            print " " + line
                        inRemBlock = False
                    out.write(line + "\n")
                    continue
                
                # Ignore extern blocks
                if inExternBlock:
                    if self.debug:
                        print " " + line
                    if line == "endextern" or line.lower() == "end extern":
                        inExternBlock = False
                    out.write(line + "\n")
                    continue
                
                # Rem
                if startswith(line, "rem"):
                    if self.debug:
                        print " " + line
                    inRemBlock = True
                    out.write(line + "\n")
                    continue
                
                # Header
                if not self.profilerLogIncludeWritten and not isHeaderLine(line):
                    #for item in self.importFiles.keys():
                    #    line += "\nImport \"" + item.replace(".bmx", ".pre.bmx") + "\"\n"
                    #line += "\nImport \"profilerLog.pre.bmx\""
                    if self.debug:
                        print " Writing profiler log include"
                    out.write("Include \"profilerLog.pre" + self.extension + "\"\n")
                    out.write("Global profiler_programTotalRunTime% = MilliSecs()\n")
                    if self.isBMAX:
                        out.write("Global profiler_memBeginning:Int = GCMemAlloced()\n")
                        out.write("OnEnd " + self.profilerLogName + "\n")
                    
                    self.profilerLogIncludeWritten = True
                
                # Function
                isMethod = startswith(line, "Method")
                isFunction = startswith(line, "Function")
                if isFunction or isMethod:
                    if self.debug:
                        if isFunction:
                            print " Found function: " + line
                        elif isMethod:
                            print " Found method: " + line
                    if not line.lower().endswith("abstract") and not "______________" in line:
                        if isFunction:
                            inFunction = True
                        else:
                            inMethod = True
                        
                        # Set start line
                        funcStartLine = lineCount
                        
                        cutFrom = line.find("(")
                        firstTypeDecl = line.find(":")
                        
                        if firstTypeDecl == -1:
                            firstTypeDecl = len(line)   # Set to maximum
                        
                        firstTypeTag1 = line.find("%")
                        if firstTypeTag1 == -1:
                            firstTypeTag1 = len(line)   # Set to maximum
                        
                        firstTypeTag2 = line.find("#")
                        if firstTypeTag2 == -1:
                            firstTypeTag2 = len(line)   # Set to maximum
                            
                        if self.isBMAX:
                            firstTypeTag3 = line.find("!")
                            if firstTypeTag3 == -1:
                                firstTypeTag3 = len(line)   # Set to maximum
                        elif self.isBB:
                            firstTypeTag3 = line.find(".")
                            if firstTypeTag3 == -1:
                                firstTypeTag3 = len(line)   # Set to maximum
                            
                        firstTypeTag4 = line.find("$")
                        if firstTypeTag4 == -1:
                            firstTypeTag4 = len(line)   # Set to maximum
                        
                        cutFrom = min(cutFrom, firstTypeDecl, firstTypeTag1, firstTypeTag2, firstTypeTag3, firstTypeTag4)
                        
                        if cutFrom == -1:
                            print "Error: Wrong function definition"
                        
                        funcName = line[findFirstWhitespace(line) + 1 : cutFrom].strip()
                        if isMethod:
                            print " Method: " + currentType + "." + funcName
                            # No [self.methodCount += 1] because methods are counted on "End Type"
                        else:
                            if currentType:
                                print " Function: " + currentType + "." + funcName
                            else:
                                print " Function: " + funcName
                            with self.lock_functionCount:
                                self.functionCount += 1
                        
                        profilerFuncName = currentType + "__" + funcName
                        
                        # TODO: SCRIPTING: FunctionBegin
                        execName = "/scripts/onFunctionBegin.py"
                        try:
                            execfile(self.app.dir + execName)
                        except IOError:
                            execfile("." + execName)
                        
                        # Script will write the line to 'out' so the global line var has to be empty
                        line = ""
                            
                # End Function
                elif line == "EndFunction" or line == "End Function" or line == "EndMethod" or line == "End Method":
                    if self.debug:
                        print " " + line + ": " + funcName
                    
                    # Add function/method to the list
                    currentTypeMethodList.append((funcName, lineCount - funcStartLine - 1))
                    
                    # TODO: SCRIPTING: FunctionEnd
                    execName = "/scripts/onFunctionEnd.py"
                    try:
                        execfile(self.app.dir + execName)
                    except IOError:
                        execfile("." + execName)
                    line = ""
                    
                    if inFunction:
                        inFunction = False
                    elif inMethod:
                        inMethod = False
                    
                # Return
                elif startswith(line, "Return"):
                    if self.debug:
                        print " Keyword: Return"
                    
                    # TODO: SCRIPTING: FunctionEnd
                    execName = "/scripts/onFunctionEnd.py"
                    try:
                        execfile(self.app.dir + execName)
                    except IOError:
                        execfile("." + execName)
                    line = ""
                
                # Type
                elif startswith(line, "Type"):
                    toChar = len(line)
                    foundEnd = line.lower().find("extends")
                    if foundEnd != -1:
                        toChar = foundEnd
                    foundEnd = line.lower().find("abstract")
                    if foundEnd != -1 and foundEnd < toChar:
                        toChar = foundEnd
                    foundEnd = line.lower().find("final")
                    if foundEnd != -1 and foundEnd < toChar:
                        toChar = foundEnd
                    currentType = line[5:toChar].strip()                   # TODO: Extends keyword
                    with self.lock_types:
                        if not currentType in self.types:
                                currentTypeMethodList = self.types[currentType] = list()
                    print " Type: " + currentType
                    
                # End Type
                elif line == "EndType" or line == "End Type":
                    if self.debug:
                        print " End Type: " + currentType
                    currentType = ""
                    with self.lock_methodCount:
                        self.methodCount += len(currentTypeMethodList)
                    with self.lock_types:
                        currentTypeMethodList = self.types[""]
                
                # Extern
                elif startswith(line, "Extern"):
                    if self.debug:
                        print " Extern: " + line
                    inExternBlock = True
                
                # If
                elif startswith(line, "If"):
                    if self.debug:
                        print " Keyword: If"
                    if self.generateProfilerData and (inFunction or inMethod):
                        pos = line.rfind("Return")
                        if pos != -1:
                            if not isVarChar(line, pos - 1) and not isVarChar(line, pos + 6):
                                line = line[:pos] + self.getFunctionProfilerEnd(profilerFuncName) + self.lineSeparator + line[pos:]
                
                # Import
                elif startswith(line, "Import"):
                    importFile = getParameter(line, 0)
                    print " Import: " + importFile
                    if importFile.startswith("\""):                     # File import
                        importFile = importFile[1:len(importFile)-1]
                        line = line[:findFirstWhitespace(line)] + " \"" + importFile.replace(self.extension, ".pre" + self.extension) + "\"" 
                        
                # Include
                elif startswith(line, "Include"):
                    importFile = getParameter(line, 0)
                    importFile = importFile[1:len(importFile)-1]
                    line = line[:findFirstWhitespace(line)] + " \"" + importFile.replace(self.extension, ".pre" + self.extension) + "\"" 
                    print " Include: " + importFile
                    
                # Global
                elif startswith(line, "Global"):
                    if self.debug:
                        print " Keyword: Global"
                    pass
                
                # End
                elif self.isBB and (startswith2(line, "End") or startswith2(line, "RuntimeError")):
                    tmpLine = line.replace(" ", "")
                    if tmpLine[3:] == "" or tmpLine[3:] == "()":
                        if self.debug:
                            print " Keyword: End"
                        line = self.profilerLogName + "\n" + line
                
                out.write(line + "\n")
                
        # Error, print line
        except:
            printTraceback("Line[" + str(lineCount) + "] = " + line)
            sys.exit()
        
        if self.isBB:
            out.write(self.profilerLogName + "\n\n")
        
        out.close()
        inp.close()
        
        # Add local lineCount to global lineCount
        with self.lock_lineCount:
            self.lineCount += lineCount
        print " Lines: " + str(lineCount)
        
